﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Accord.Audio;
using AForge.Math;
using TimbreRecognition.FeatureExtraction;

namespace TimbreRecognition.Extraction
{
    public class FeatureExtractor : IFeatureExtractor
    {
        private const int SignalRate = 44100;

        private readonly string[] classes;
        //private MFCCAdapter computer;
        private MelCepstrum melCepstrum;

        public FeatureExtractor(int numberOfCoeficients)
        {
           // computer = new MFCCAdapter(numberOfCoeficients);
            melCepstrum = new MelCepstrum(SignalRate, numberOfCoeficients);
        }

        public FeatureExtractor(int NumberOfCoeficients, string[] classes) : this(NumberOfCoeficients)
        {
            this.classes = classes;            
        }

        public List<Feature> Extract(IEnumerable<string> files, int samplesPerFrame, float overlapping)
        {    
            List<Feature> featureList = new List<Feature>();
          
            foreach (string fileName in files)
            {
                FileInfo fieInfo = new FileInfo(fileName);
                if (classes.Any(o => fieInfo.Name.StartsWith(o)))
                {
                    List<Feature> features = Extract(fileName, samplesPerFrame, overlapping, true);
                    featureList.AddRange(features);
                }
            }

            return featureList;
        }

        private double[] GetPowerSpectrum(Signal signal)
        {
            ComplexSignal complex = ComplexSignal.FromSignal(signal);
            Complex[] channel = complex.GetChannel(0);
            return Accord.Audio.Tools.GetPowerSpectrum(channel);
        }

        private int GetClassIndex(string fileName)
        {
            for (int i = 0; i < classes.Length; i++)
            {
                if (fileName.ToUpper().StartsWith(classes[i].ToUpper()))
                {
                    return i;
                }
            }

            throw new ArgumentException("No class found");
        }

        private void CheckSampleLenght(int sampleLenght)
        {
            int power = (int)(Math.Log(sampleLenght) / Math.Log(2));
            
            if (sampleLenght != (1 << power))
            {
                throw new ArgumentException("number of samples must be power of 2");
            }
        }


        public List<Feature> Extract(string fileName, int samplesPerFrame, float overlapping, bool withClassInfo = false)
        {
            CheckSampleLenght(samplesPerFrame);    // Make sure n is a power of 2            

            double[] classInfo = null;

            string instrument = null;

            string note = null;

            if (withClassInfo)
            {
                FileInfo fileInfo = new FileInfo(fileName);

                classInfo = new double[classes.Length];
                int classIndex = GetClassIndex(fileInfo.Name);
                classInfo[classIndex] = 1;

                //имя файла без расширения
                string shortFileName = fileInfo.Name.Substring(0, fileInfo.Name.Length - 4);

                string[] parts = shortFileName.Split('_');

                instrument = parts[0];

                note = parts[1] + parts[2];
            }

            //MelCepstrum melCepstrum = new MelCepstrum(samplesPerFrame, 44100, 12);

            Window window = new Window(samplesPerFrame);

            List<float[]> signals = WaveUtil.GetPreparedData(fileName, samplesPerFrame, overlapping);
            List<Feature> features = new List<Feature>();
            foreach (float[] signal in signals)
            {
                window.applyWindow(signal);

                double[] mfcc = melCepstrum.doMFCC(signal);

                int zcr = GetZCR(signal);

                Feature feature = new Feature
                {
                    MFCC = mfcc,
                    ZCR = zcr,
                    ClassInfo = classInfo, 
                    Instrument = instrument,
                    Note = note
                };

                FFT fft = new FFT();
                fft.computeFFT(signal);

                feature.PowerSpectrum = GetSpectrum(fft.real, fft.imag);

                features.Add(feature);
            }

            //List<Signal> signals = WaveUtil.Split(fileName, samplesPerFrame);
            //List<Feature> features = new List<Feature>();
            //foreach (Signal signal in signals)
            //{
            //    double[] mfcc = computer.Compute(signal);

            //    Feature feature = new Feature();

            //    feature.MFCC = mfcc;
            //    feature.ClassInfo = classInfo;

            //    feature.PowerSpectrum = GetPowerSpectrum(signal);

            //    features.Add(feature);
            //}

            return features;
        }

        public double[] Extract(float[] data)
        {
            return melCepstrum.doMFCC(data);
        }

        private double[] GetSpectrum(float[] real, float[] imaginary)
        {
            double[] spectrum = new double[real.Length / 2 + 1];
            for (int i = 0; i < spectrum.Length; i++)
            {
                spectrum[i] = Math.Pow(Math.Sqrt(Math.Pow(real[i], 2) + Math.Pow(imaginary[i], 2)), 2) / real.Length;
            }

            return spectrum;
        }

        private int GetZCR(float[] signal)
        {
            float previous = signal[0];
            return signal.Count(data => (previous > 0 && data <= 0) || (previous < 0 && data >= 0));
        }
    }
}
